#include "svm_predict.h"

// Define the external variables
struct svm_node *x = nullptr;
int max_nr_attr = 64;
double predict_label = 0.0;
double tmpMaxPredict = 0.0;
struct svm_model* model = nullptr;
int predict_probability = 1; // default: 0
char *line = nullptr;
int max_line_len = 1024;

static char *readline(File input)
{
    int len;

    // if (fgets(line, max_line_len, input) == NULL)
    if (!readLineFromSD(input, max_line_len, line))
        return NULL;

    while (strrchr(line, '\n') == NULL)
    {
        max_line_len *= 2;
        line = (char *)realloc(line, max_line_len);
        len = (int)strlen(line);
        if (!readLineFromSD(input, max_line_len - len, line + len))
            ;
        // if (fgets(line + len, max_line_len - len, input) == NULL)
        break;
    }
    return line;
}

void exit_input_error(int line_num)
{
    // fprintf(stderr, "Wrong input format at line %d\n", line_num);
    Serial.println("Wrong input format at line " + String(line_num));
    // exit(1);
}

void predict(File input, File output)
{
    LOG("\n\nbegin to predict\n");
    int correct = 0;
    int total = 0;
    double error = 0;
    double sump = 0, sumt = 0, sumpp = 0, sumtt = 0, sumpt = 0;

    int svm_type = svm_get_svm_type(model);
    LOG("svm_type: ", String(svm_type), "\n");
    int nr_class = svm_get_nr_class(model);
    LOG("nr_class: ", String(nr_class), "\n");
    double *prob_estimates = NULL;
    int j;
    tmpMaxPredict = 0;

    if (predict_probability)
    {
        if (svm_type == NU_SVR || svm_type == EPSILON_SVR)
        {
        }
        // info("Prob. model for test data: target value = predicted value + z,\nz: Laplace distribution e^(-|z|/sigma)/(2sigma),sigma=%g\n", svm_get_svr_probability(model));
        else
        {
            int *labels = (int *)malloc(nr_class * sizeof(int));
            svm_get_labels(model, labels);
            prob_estimates = (double *)malloc(nr_class * sizeof(double));
            LOG("labels");
            // cout << "labels "; // cout
            for (j = 0; j < nr_class; j++)
            {
                LOG(labels[j], " ");
            }
            LOG("\n");
            free(labels);
        }
    }

    max_line_len = 1024;
    // max_line_len = 6144;
    line = (char *)malloc(max_line_len * sizeof(char));
    while (readline(input) != NULL)
    {
        int i = 0;
        double target_label;
        char *idx, *val, *label, *endptr;
        int inst_max_index = -1; // strtol gives 0 if wrong format, and precomputed kernel has <index> start from 0

        label = strtok(line, " \t\n");
        if (label == NULL)
        { // empty line
            LOG("empty line\n");
            exit_input_error(total + 1);
        }
        target_label = strtod(label, &endptr);
        if (endptr == label || *endptr != '\0')
        {
            LOG("wrong label format\n");
            exit_input_error(total + 1);
        }
        while (1)
        {
            if (i >= max_nr_attr - 1) // need one more for index = -1
            {
                max_nr_attr *= 2;
                x = (struct svm_node *)realloc(x, max_nr_attr * sizeof(struct svm_node));
            }

            idx = strtok(NULL, ":");
            val = strtok(NULL, " \t");

            if (val == NULL)
                break;
            errno = 0;
            x[i].index = (int)strtol(idx, &endptr, 10);
            if (endptr == idx || errno != 0 || *endptr != '\0' || x[i].index <= inst_max_index)
            {
                LOG("Wrong index at line ", String(total + 1), "\n");
                exit_input_error(total + 1);
            }
            else
                inst_max_index = x[i].index;

            errno = 0;
            x[i].value = strtod(val, &endptr);
            if (endptr == val || errno != 0 || (*endptr != '\0' && !isspace(*endptr)))
            {
                LOG("Wrong value at line ", String(total + 1), "\n");
                exit_input_error(total + 1);
            }

            ++i;
        }
        x[i].index = -1;
        if (predict_probability && (svm_type == C_SVC || svm_type == NU_SVC))
        {
            // Serial.println("print result");
            predict_label = svm_predict_probability(model, x, prob_estimates);

            // fprintf(output, "%g", predict_label);
            LOG(predict_label, " ");
            // cout << "???label???????"; // cout
            for (j = 0; j < nr_class; j++)
            {
                // fprintf(output, " %g", prob_estimates[j]);
                Serial.print(prob_estimates[j], 5);
                Serial.print(" ");
                // LOG(prob_estimates[j], " ");
                tmpMaxPredict = tmpMaxPredict > prob_estimates[j] ? tmpMaxPredict : prob_estimates[j];
                // cout << prob_estimates[j] * 100 << "%"; // cout prob_estimates
                if (j != nr_class - 1)
                {
                    // cout << " ";
                }
            }
            // cout << endl << "???????" << gesture[int(predict_label)] << "(" << char('a' + int(predict_label)) << "):" << tmpMaxPredict * 100 << "%" << endl;

            // fprintf(output, "\n");
            LOG("\n");
        }
        else
        {
            predict_label = svm_predict(model, x);
            // fprintf(output, "%.17g\n", predict_label);
            LOG(predict_label, "\n");
        }

        if (predict_label == target_label)
            ++correct;
        error += (predict_label - target_label) * (predict_label - target_label);
        sump += predict_label;
        sumt += target_label;
        sumpp += predict_label * predict_label;
        sumtt += target_label * target_label;
        sumpt += predict_label * target_label;
        ++total;
    }
    if (svm_type == NU_SVR || svm_type == EPSILON_SVR)
    {
        /*info("Mean squared error = %g (regression)\n", error / total);
        info("Squared correlation coefficient = %g (regression)\n",
            ((total*sumpt - sump*sumt)*(total*sumpt - sump*sumt)) /
            ((total*sumpp - sump*sump)*(total*sumtt - sumt*sumt))
        );*/
    }
    /*else
        info("Accuracy = %g%% (%d/%d) (classification)\n",
        (double)correct / total * 100, correct, total);*/
    if (predict_probability)
        free(prob_estimates);
}

void exit_with_help()
{
    printf(
        "Usage: svm-predict [options] test_file model_file output_file\n"
        "options:\n"
        "-b probability_estimates: whether to predict probability estimates, 0 or 1 (default 0); for one-class SVM only 0 is supported\n"
        "-q : quiet mode (no outputs)\n");
    exit(1);
}

int svmPredict(int argc, char **argv)
{
    LOG("begin to predict\n");
    argc = 4;

    int i;
    for (i = 1; i < argc; i++)
    {
        if (argv[i][0] != '-')
            break;
        ++i;
        switch (argv[i - 1][1])
        {
        case 'b':
            predict_probability = atoi(argv[i]);
            break;
        case 'q':
            // info = &print_null;
            i--;
            break;
        default:
            LOG("Unknown option: -" + String(argv[i - 1][1]), "\n");
            exit_with_help();
            exit_with_help();
        }
    }

    if (i >= argc - 2)
        exit_with_help();

    File input = SD.open(argv[i], FILE_READ);
    if (!input)
    {
        LOG("Error opening " + String(argv[i]), "\n");
        // fprintf(stderr, "can't open input file %s\n", argv[i]);
        // exit(1);
    }
    LOG("input file init success\n");

    File output = SD.open(argv[i + 2], FILE_WRITE);
    if (!output)
    {
        LOG("Error opening " + String(argv[i + 2]), "\n");
        // fprintf(stderr, "can't open output file %s\n", argv[i + 2]);
        // exit(1);
    }
    LOG("output file init success\n");

    //  加载模型
    model = svm_load_model(argv[i + 1]);
    LOG("model file init success\n");

    if (model == 0)
    {
        LOG("can't open model file", String(argv[i + 1]), "\n");
        // fprintf(stderr, "can't open model file %s\n", argv[i + 1]);
        // exit(1);
    }
    
    x = Malloc(svm_node, max_nr_attr);
    // x = (struct svm_node *)malloc(max_nr_attr * sizeof(struct svm_node));
    LOG("malloc x success\n");
    if (predict_probability)
    {
        LOG("predict_probability\n");
        if (svm_check_probability_model(model) == 0)
        {
            LOG("Model does not support probabiliy estimates\n");
            // fprintf(stderr, "Model does not support probabiliy estimates\n");
            // exit(1);
        }
    }
    else
    {
        LOG("predict_probability else\n");
        if (svm_check_probability_model(model) != 0)
        {
            LOG("Model supports probability estimates, but disabled in prediction\n");
        }
    }

    LOG("predict(input, output) begin\n");
    //  进行推理
    predict(input, output);
    LOG("predict(input, output) end\n");
    svm_free_and_destroy_model(&model);
    Free(x);
    free(line);
    // fclose(input);
    // fclose(output);
    input.close();
    output.close();
    return 0;
}
